#include <linux/in.h>
#include <net/route.h>
#include <net/flow.h>
#include <net/icmp.h>
#include <net/ip.h>
#include <linux/netfilter.h>
#include <net/dst.h>
#include <net/ip6_route.h>

#include "socs/comm/socs.h"
#include "socs/mod/transmit.h"

#include "socs/mod/neigh.h"
#include "socs/mod/addr_opt.h"

int find_destination_ip(void *saddr, void *daddr, socs_addr_t *dIP, int family, int inner_family)
{
	int is_localnode = 0;

	if (socs_neigh_find_by_addr(saddr, daddr, family, inner_family, &is_localnode, dIP) < 0) {
		log_err(ERR_TRANS_FAILED, "socs_neigh find by addr failed\n");
		return TRANS_FAILED;
	}

	if (is_localnode) {
		return TRANS_LOCAL;
	}

	return TRANS_SUCCESS;
}

int change_destination4(struct sk_buff *skb, struct in_addr *daddr)
{
	struct rtable *rt;                      /* Route to the other host */
	struct flowi4 fl4;
	struct iphdr  *iph = ip_hdr(skb);
	struct net *net;

	if (!skb_dst(skb)) {
		if (!skb->dev) {
			net = &init_net;
		} else {
			net = dev_net(skb->dev);
		}
	} else {
		log_debug(" skb_dst is no  null\n");
		net = dev_net(skb_dst(skb)->dev);
	}

	memset(&fl4, 0, sizeof(fl4));
	fl4.daddr = (*(__be32 *)daddr);
	log_debug("fl4.daddr:%pI4,daddr:%pI4\n", &fl4.daddr, daddr);
	fl4.flowi4_tos =  RT_TOS(iph->tos);
	fl4.flowi4_flags = FLOWI_FLAG_KNOWN_NH;
	rt = ip_route_output_key(net, &fl4);

	if (!rt) {
		goto tx_error_icmp;
	}

	/* MTU checking */
	/*mtu = dst_mtu(&rt->dst);
	if ((iph->frag_off & htons(IP_DF)) && skb->len > mtu &&
	    !skb_is_gso(skb)) {
	        icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
	        goto tx_error;
	}*/
	/* drop old route */
	log_debug("change destination4 rt->dst:%p,rt->dst.output:%p,rt->gw:%pI4\n",
			  &rt->dst, rt->dst.output, &rt->rt_gateway);
	ip_send_check(ip_hdr(skb));
	skb_dst_drop(skb);
	skb_dst_set(skb, &rt->dst);
	/* Another hack: avoid icmp_send in ip_fragment */
	return TRANS_SUCCESS;
tx_error_icmp:
	dst_link_failure(skb);
//tx_error:
	kfree_skb(skb);
	return TRANS_FAILED;
}
int change_destination6(struct sk_buff *skb, struct in6_addr *daddr)
{
	struct flowi6 fl6 ;
	struct net *net;
	struct rt6_info *rt;
	struct dst_entry *dst;

	if (!skb_dst(skb)) {
		if (!skb->dev) {
			net = &init_net;
		} else {
			net = dev_net(skb->dev);
		}
	} else {
		log_debug(" skb_dst is no  null\n");
		net = dev_net(skb_dst(skb)->dev);
	}

	fl6.daddr = *daddr;
	log_debug("fl6.daddr:%pI6c,daddr:%pI6c\n", &fl6.daddr, daddr);
	dst = ip6_route_output(net, NULL, &fl6);

	if (!dst) {
		goto tx_error_icmp;
	}

	rt = (struct rt6_info *)dst;
	skb = skb_share_check(skb, GFP_ATOMIC);

	if (unlikely(skb == NULL)) {
		dst_release(dst);
		return TRANS_FAILED;
	}

	log_debug("change destination6 rt->dst:%p,rt->dst.output:%p,&rt->rt_gateway:%pI6\n",
			  &rt->dst, rt->dst.output, &rt->rt6i_gateway);
	/* drop old route */
	skb_dst_drop(skb);
	skb_dst_set(skb, &rt->dst);
	return TRANS_SUCCESS;
tx_error_icmp:
	dst_link_failure(skb);
//tx_error:
	kfree_skb(skb);
	return TRANS_FAILED;
}

int packet_send(int family, struct sk_buff *skb)
{
	int pf;
	int err;

	if (family == IPV4) {
		pf = AF_INET;
	} else {
		pf = AF_INET6;
	}

	skb_forward_csum(skb);
	//NF_HOOK(pf, NF_INET_LOCAL_OUT, skb, NULL, skb_dst(skb)->dev,dst_output);
	err = dst_output(skb);

	if (err) {
		log_err(ERR_SOCKET, "packet forward failed");
		//kfree_skb(skb); //mem dst_output(skb); release by manual
		//0630 remove first, it seems make error
	} else {
		log_debug("packet forward success");
	}

	return TRANS_SUCCESS; //mem be careful , outside call should return NF_STOLEN
}
